package spitfire.ksim.node;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import spitfire.ksim.data.KSensorSnapshot;
import spitfire.ksim.simulator.KSimulator;
import TJavaLib.TList;

/**
 * <p>
 * The KSensor class represents a single sensor. 
 * A sensor has a type information, and can provide a series of sensed data with 
 * corresponding time stamp. It may also have an optional semantic description.
 * </p>
 * 
 * <p>The data is populated when loading from sensor node profile.</p>
 * 
 * @author Adam
 *
 */
public class KSensor {
	private static final double THRESHOLD_COE = 0.8;
	private static final int MOVING_AVERAGE_WINDOW = 5; //
	private TList rawLen = new TList();
	private int averageLen = 0;
	
	private String sensorType;
	private KSimulator simulator;
	private String description; 
	private List<Long> timeStampList = new ArrayList<Long>();
	private List<Double> dataList = new ArrayList<Double>();
	
	public KSensor(KSimulator simulator) 
	{
		this.simulator = simulator;
	}
	
	public KSensor(String sensorType, KSimulator simulator) {
		this.sensorType = sensorType;
		this.simulator = simulator;
	}
	
	public void setSensorType(String sensorType) {
		this.sensorType = sensorType;
	}

	public String getSensorType() {
		return sensorType;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getDescription() {
		return description;
	}

	/**
	 * <p>
	 * Add a time and data value pair to the sensor.
	 * </p>
	 * <p>
	 * <b>Attention:</b> data pair added should follow the increase order with
	 * respect to time stamp, otherwise the behavior of
	 * {@link #takeSnapshot(long, long)} is unpredictable
	 * </p>
	 * 
	 * @param timeStamp - time stamp of the pair to be added.
	 * @param value - data value of the pair to be added.
	 */
	public void addTimeAndData(Long timeStamp, Double value) {
		getTimeStampList().add(timeStamp);
		getDataList().add(value);
	}
	
	/**
	 * Take a snapshot of time and data from a given start time to a given end
	 * time.
	 * 
	 * @param fromTime
	 *            - start time of the snapshot.
	 * @param toTime
	 *            - end time of the snapshot.
	 * @return {@link KSensorSnapshot} that consists of data between start
	 *         time(included if there is a data pair on that time) and end
	 *         time(excluded if there is a data pair on that time).
	 */
	public KSensorSnapshot takeSnapshot(long fromTime, long toTime) {
		int fromIndex = Collections.binarySearch(getTimeStampList(), fromTime);
		int toIndex = Collections.binarySearch(getTimeStampList(), toTime);
		if (fromIndex < 0) {
			// not found, convert to insertion point. @see Collections.binarySearch; 
			fromIndex = -fromIndex - 1;
		}
		if (toIndex < 0) {
			toIndex = -toIndex - 1;
		}
		List<Long> timeSnapshot = new ArrayList<Long>(getTimeStampList().subList(fromIndex, toIndex));
		List<Double> valueSnapshot = new ArrayList<Double>(getDataList().subList(fromIndex, toIndex));
		return new KSensorSnapshot(timeSnapshot, valueSnapshot);
	}
	
	/**
	 * Chech whether the raw data is sufficient, here 80% 
	 * @param sensorSnapshot
	 * @return
	 */
	public boolean isSufficient(KSensorSnapshot sensorSnapshot){
		boolean flag = true;
		int size = sensorSnapshot.getTimeStampList().size();
		if (size == 0) {
			return false;
		}
		long timeMin = sensorSnapshot.getTimeStampList().get(0);
		long timeMax = sensorSnapshot.getTimeStampList().get(size - 1);

		// System.err.println("type="+type+"averageLen*0.8="+(averageLen*0.8)+"data.len="+sData.len());
		if ((timeMax * 1.0 - timeMin) < simulator.getDynamicTimeWindow() * THRESHOLD_COE
				|| size < averageLen * THRESHOLD_COE) {
			flag = false;
		}
		if (rawLen.len() == 0) {
			rawLen.enQueue(size);
			averageLen = size;
		} else if (rawLen.len() < MOVING_AVERAGE_WINDOW) {
			rawLen.enQueue(size);
			averageLen = (averageLen * (rawLen.len() - 1) + size)
					/ rawLen.len();
		} else if (rawLen.len() >= MOVING_AVERAGE_WINDOW) {
			int M = (Integer) rawLen.deQueue();
			rawLen.enQueue(size);
			averageLen = averageLen - M / MOVING_AVERAGE_WINDOW + size
					/ MOVING_AVERAGE_WINDOW;
		}
		// System.err.println("type="+type+"falg="+flag);
		return flag;
	}

	public List<Long> getTimeStampList() {
		return timeStampList;
	}
	
	public List<Double> getDataList() {
		return dataList;
	}
}
